
`include "common_header.verilog" 

//  *************************************************************************
//  File : module_mdio_shift.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited. 
//  Copyright (c) 2008 Morethanip, 85757 Karlsfeld, Germany
//  Designed by Daniel Koehler
//  info@morethanip.com
//  *************************************************************************
//  Description: MDIO Module frame shifting the serial data out to the bus interface.
//
//  Version    : $Id: module_mdio_shift.v,v 1.2 2009/10/15 10:04:02 dp Exp $
//  *************************************************************************

module module_mdio_shift (

  input         reset_pclk,             // async active high
  input         pclk,                   // register clock
  input         clk_ena,                // clock enable 
        
        // config

  input [31:0]  reg_data_in,     // data to write
  input         mmfr_wr,        // write data into mmfr
  output [31:0] reg_mmfr,       // the register contents currently
  input         dis_pre,        // disable preamble
  input         startt,         // start trigger
  output reg    mdio_busy,      // busy indicator

        // MDIO serial pin
        
  input         mdio_in,
  output reg    mdio_out,
  output reg    mdio_oen        

);

reg     [31:0]  mmfr;

parameter       LOC_STATE_IDLE = 2'b 00;
parameter       LOC_STATE_PRBL = 2'b 01;
parameter       LOC_STATE_WRITE= 2'b 10;
parameter       LOC_STATE_READ = 2'b 11;

reg   [1:0]     state;
reg   [1:0]     nextstate;
reg   [4:0]     cnt;
reg             is_read;
reg             do_shift;
reg             do_read;
reg             mdio_in_r;

// MDIO input register 
// (optional, we sample the input always just before the mdc rising edge so data is stable long time before)
// -------------------
always @(posedge reset_pclk or posedge pclk)
begin : p_ir
        if (reset_pclk == 1'b 1)
        begin

                mdio_in_r <= 1'b 0;

        end
        else
        begin

                mdio_in_r <= mdio_in;

        end
end



// frame data register
// -------------------

assign reg_mmfr = mmfr; // provide read data back

always @(posedge reset_pclk or posedge pclk)
begin : p_mmfr
        if (reset_pclk == 1'b 1)
        begin
                mmfr <= 32'h 00000000;
                is_read   <= 1'b 0;
        end
        else
        begin
                if (mmfr_wr==1'b 1)
                begin

                        mmfr <= reg_data_in;
                        is_read <= reg_data_in[29]; // remember if we have a read transaction or write otherwise

                end
                else if( clk_ena==1'b 1 & do_shift==1'b 1 )
                begin
                        // shift all 32 bits out
                        
                        if( do_read==1'b 1 )
                        begin
                                mmfr[0] <= mdio_in_r;
                        end
                        else
                        begin
                                mmfr[0] <= mmfr[31];
                        end
                        
                        mmfr[31:1] <= mmfr[30:0];       // shift through msb
                
                end
        
        
        end
end

//  Main State Machine
//  ------------------
always @(posedge reset_pclk or posedge pclk)
   begin : p_stmr
   if (reset_pclk == 1'b 1)
      begin
      state <= LOC_STATE_IDLE;	
      end
   else
      begin
        if( clk_ena==1'b 1 )
        begin
                state <= nextstate;	
        end
        
      end
   end

always @(state or dis_pre or startt or cnt or is_read)
begin : p_stm
    case (state)

        LOC_STATE_IDLE:
        begin
                
                if( startt==1'b 1 & dis_pre==1'b 0 )
                begin

                        nextstate = LOC_STATE_PRBL;

                end
                else if( startt==1'b 1 & dis_pre==1'b 1)
                begin
                        // skip preamble
                        nextstate = LOC_STATE_WRITE;

                end
                else
                begin

                        nextstate = LOC_STATE_IDLE;

                end
                
        end

        // send 32 bits of preamble
        // -------------
        LOC_STATE_PRBL:
        begin
                if( cnt==5'b 11111 )
                begin
                        
                        nextstate = LOC_STATE_WRITE;

                end
                else
                begin

                        nextstate = LOC_STATE_PRBL;

                end
        end
        
        // write bits out to MDIO bus
        // -------------
        LOC_STATE_WRITE:
        begin
                if( cnt==5'd 14 & is_read==1'b 1)
                begin
                        // change to read transaction at start of TA after 14 bits written
                        nextstate = LOC_STATE_READ;

                end
                else if( cnt==5'b 11111 )
                begin
                        // end of write transaction
                        nextstate = LOC_STATE_IDLE;

                end
                else
                begin

                        nextstate = LOC_STATE_WRITE;

                end
        end

        // read bits in from MDIO bus
        // -------------
        LOC_STATE_READ:
        begin
                if( cnt==5'd 16 )       // 2 TA phase, 16 data phase (totalling to 33 cycles as we have last bit from phy sampling at the 33rd)
                begin
                        // end of read transaction
                        nextstate = LOC_STATE_IDLE;

                end
                else
                begin

                        nextstate = LOC_STATE_READ;

                end
        end


        default:
        begin
                nextstate = LOC_STATE_IDLE;     // never reached
        end

    endcase

end


// Control
// -----------
always @(posedge reset_pclk or posedge pclk)
begin : p_ctrl
        if (reset_pclk == 1'b 1)
        begin

                cnt       <= 5'h 0;
                mdio_busy <= 1'b 0;
                do_shift  <= 1'b 0;
                do_read   <= 1'b 0;
                
        end
        else
        begin
                if( clk_ena==1'b 1 )
                begin

                        if( state!=nextstate | state==LOC_STATE_IDLE) 
                        begin

                                cnt <= 5'h 0;

                        end
                        else if( cnt != 5'b 11111 )
                        begin

                                cnt <= cnt + 5'h 1;

                        end



                        // provide busy indication for handshake and MII interrupt generation
                        if( nextstate==LOC_STATE_IDLE & state==LOC_STATE_IDLE )
                        begin

                                mdio_busy <= 1'b 0;

                        end
                        else
                        begin

                                mdio_busy <= 1'b 1;

                        end

                        // instruct shifter to work. 
                        // There is a pause of 1 cycle when we switch from write to read as will sample the PHY data 
                        // with the next mdc rising edge only.
                        
                        if( state == LOC_STATE_READ | nextstate == LOC_STATE_WRITE )
                        begin

                                do_shift <= 1'b 1;

                        end
                        else
                        begin

                                do_shift <= 1'b 0;

                        end

                        // determine when to read data from MDIO into the shift register
                                                                                        
                        if( state==LOC_STATE_READ )  
                        begin
                                do_read <= 1'b 1;
                        end
                        else
                        begin

                                do_read <= 1'b 0;

                        end
                
                end // clk_ena   
        
        end
end

// MDIO bus signals
// ----------------
always @(posedge reset_pclk or posedge pclk)
begin : p_mdio
        if (reset_pclk == 1'b 1)
        begin

                mdio_out <= 1'b 0;
                mdio_oen <= 1'b 1;

        end
        else
        begin
                if (clk_ena==1'b 1)
                begin

                        if( state==LOC_STATE_IDLE | state==LOC_STATE_PRBL )
                        begin

                                mdio_out <= 1'b 1;

                        end
                        else
                        begin

                                mdio_out <= mmfr[31];   // we shift MSB first

                        end
                        
                        if( state==LOC_STATE_READ | nextstate==LOC_STATE_READ | (state==LOC_STATE_IDLE & nextstate==LOC_STATE_IDLE))
                        begin
                                // tristate the bus
                                mdio_oen <= 1'b 1;

                        end
                        else
                        begin
                                // drive output
                                mdio_oen <= 1'b 0;

                        end

                end
        end
end



endmodule
